/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package idea.sfo.viewer.designer;


import designersfo.ConsV;
import designersfo.EmptyV;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.TreeMap;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;


/**
 *
 * @author renke002
 */
public class DesignerRowChildren extends Children.Keys<DesignerRow>
{

    /**
     * Integer defining the first element of an array.
     */
    public final static int FIRST_ELEMENT = 0;
    /**
     * Mapping between the column number and its width.
     *
     * Due to the structure of the SFO file, the widths of all the columns are
     * stored as attributes to the last row (of null/empty cells).
     */
    private TreeMap< Integer , Long> colWidths;
    /**
     * Mapping between a Row Node and its corresponding Column Nodes.
     */
    private HashMap< DesignerRowNode , HashSet<DesignerColNode>> rowColNodeMap;
    /**
     * Mapping between the row number and its height.
     */
    private TreeMap< Integer , Long> rowHeigths;


    /**
     *
     * @param docRoot - Root element of the SFO XML file.
     */
    public DesignerRowChildren( List<Object> docRoot )
    {
        if ( null != docRoot && docRoot.isEmpty() == false )
        {
            //
            this.colWidths = new TreeMap<>();
            //
            this.rowHeigths = new TreeMap<>();
            //
            TreeMap< Integer , ConsV> rowMap = this.parseConsVList( docRoot , new TreeMap< Integer , ConsV>() );
            //
            //
            this.rowColNodeMap = this.buildRowColNodeMapping( rowMap );
        }
        else
        {
            // not good.
            Exceptions.printStackTrace( new Exception( "The list provided is null or empty." ) );
        }
    }


    /**
     * Computes a set of Children.Keys<DesignerRow>
     *
     */
    @Override
    protected void addNotify()
    {
        super.addNotify();

        ArrayList<DesignerRow> drList = new ArrayList<>();

        /**
         * Since this we need to display the elements on a canvas, which is
         * displayed before calling this function, the objects have already been
         * created and encapsulated within their nodes.
         */
        for ( DesignerRowNode rowNode : this.rowColNodeMap.keySet() )
        {
            //
            drList.add( rowNode.getDesignerRow() );
        }

        //
        DesignerRow[] rows = new DesignerRow[ drList.size() ];
        rows = drList.toArray( rows );

        //
        this.setKeys( rows );
    }


    /**
     *
     * @param rowMap
     */
    private HashMap< DesignerRowNode , HashSet<DesignerColNode>> buildRowColNodeMapping( TreeMap< Integer , ConsV> rowMap )
    {
        /*
         * Cumulative heights of the previous rows. This will tell the row where
         * its position on the canvas is.
         */
        long prevRowHeights = 0;

        DesignerColChildren dcc;
        DesignerRowNode drn;

        // mapping between a DesignerRowNode and the DesignerColNode(s) in its row.
        HashMap< DesignerRowNode , HashSet<DesignerColNode>> nodeMap = new HashMap<>();
        //
        for ( Integer rowNumber : rowMap.keySet() )
        {
            //
            DesignerRow dr = new DesignerRow( rowNumber , rowMap.get( rowNumber ) , this.colWidths , prevRowHeights );
            //
            this.rowHeigths.put( rowNumber , dr.getRowHeight() );
            //
            prevRowHeights += dr.getRowHeight();
            //
            dcc = new DesignerColChildren( dr );
            drn = new DesignerRowNode( dr , dcc );
            nodeMap.put( drn , dcc.getDesignerColNodes() );
        }
        return nodeMap;
    }


    /**
     * Create nodes for a given key.
     *
     *
     * @param key - the key
     * @return child nodes for this key or null if there should be no nodes for
     * this key
     */
    @Override
    protected Node[] createNodes( DesignerRow dRow )
    {
//        return new Node[]
//                {
//                    new DesignerRowNode( dRow )
//                };
        // change this function so the DesignerRowNode is instantiated in the 
        // constructor, stored locally, and retrieved here when needed.
        // doing this to get the DesignerColNode(s) contained within the 
        // DesignerRowNode.
        Node[] theseNodes =
        {
            this.getRowNodeByNumber( dRow.getRowNumber() ) ,
            this.getRowNodeByNumber( dRow.getRowNumber() )
        };
        return theseNodes;
    }


    /**
     *
     * @return Column Number to Width mapping
     */
    public TreeMap< Integer , Long> getColWidths()
    {
        return this.colWidths;
    }


    /**
     *
     * @return
     */
    public HashMap< DesignerRowNode , HashSet<DesignerColNode>> getNodeMapping()
    {
        return this.rowColNodeMap;
    }


    /**
     *
     * @return Row Number to Height mapping
     */
    public TreeMap< Integer , Long> getRowHeights()
    {
        return this.rowHeigths;
    }


    /**
     *
     * @param rowNumber
     *
     * @return the DesignerRowNode at the specified row or null if none was
     * found.
     */
    public DesignerRowNode getRowNodeByNumber( Integer rowNumber )
    {
        for ( DesignerRowNode drn : this.rowColNodeMap.keySet() )
        {
            if ( drn.getRowNumber().equals( rowNumber ) )
            {
                return drn;
            }
        }
        return null;
    }


    /**
     *
     *
     * @param cvs - list of ConsV objects.
     */
    private TreeMap< Integer , ConsV> parseConsVList( List<?> cvs , TreeMap< Integer , ConsV> rowConsVMap )
    {
        //
        Object o;

        Iterator<?> cvs_it = cvs.iterator();
        while ( cvs_it.hasNext() )
        {
            /**
             * the list holds several different objects, so we need to cast each
             * one to Object and then check it with 'intanceof' since we're only
             * interested in ConsV objects.
             */
            o = cvs_it.next();
            if ( o instanceof ConsV )
            {
                ConsV row = (ConsV) o;

                rowConsVMap.put( new Integer( rowConsVMap.size() ) , row );

                if ( row.getEmptyV().isEmpty() == false )
                {
                    //
                    this.parseEmptyVList( row.getEmptyV().get( FIRST_ELEMENT ) );
                }
                else
                {
                    rowConsVMap = this.parseConsVList( row.getConsV() , rowConsVMap );
                }

            }
            else if ( o instanceof EmptyV )
            {
                /*
                 * represents the end-of-column marker cells located at the
                 * bottom of the designer grid, they contain 'col-info' cells
                 * that store the width of the column they represent.
                 */
                this.parseEmptyVList( (EmptyV) o );
            }
            else
            {
                // some other object we're not interested in.
                System.out.println( "Something else." );
            }
        }
        return rowConsVMap;
    }


    /**
     *
     * @param ev
     */
    private void parseEmptyVList( EmptyV ev )
    {
        if ( ev.getColInfo().isEmpty() == false )
        {
            String fu_size = ev.getColInfo().get( FIRST_ELEMENT ).getCellSizeH();
            this.colWidths.put( this.colWidths.size() , new Long( fu_size.replace( "fu" , "" ) ) );
        }
        if ( ev.getEmptyV().isEmpty() == false )
        {
            parseEmptyVList( ev.getEmptyV().get( FIRST_ELEMENT ) );
        }
    }

}
